/******************************************* */
// 类型断言
// 类型断言就是手动指定一个值的类型，一般用于可能会有多种类型的选择的时候
// 使用方式：值 as 类型，或者<类型>值。为了考虑兼容性，建议使用as来做类型断言
// 联合类型中使用类型断言
function fun1(x: string | number): void {
  console.log((x as string).split('').reverse().join());
}
// 使用类型断言需要注意：类型断言只能解决编译时的报错，但是无法避免运行时的报错。合理使用断言，避免断言后调用方法或者深层次引用属性，减少不必要的运行时错误

// 将一个父类断言为更加具体的子类
// 当类之间存在继承关系时，可以使用类型断言
class AError extends Error {
  code: number = 999
}
class BError extends Error {
  status: number = 200
}
// function isAError(error: Error) {
//   if (error instanceof AError) {
//     return true;
//   }
//   return false;
// } // 更推荐
function isAError(error: Error) {
  if (typeof (error as AError).code === 'number') {
    return true;
  }
  return false;
}
// 当接口之间存在继承关系，也可以使用类型断言
interface AError extends Error {
  code: number;
}
interface BError extends Error {
  status: number;
}
function isAError2(error: Error) {
  if (typeof (error as AError).code === 'number') {
    return true;
  }
  return false;
}

// 将任何一个类型断言为any
// any表示任意类型，一旦某个值的类型被断言为any，那么将可以访问任何属性和方法
// 尽管如此，若非必要，不建议将类型断言为any
(window as any).foo = 1;

// 将任意类型any断言为一个具体的类型
// 遇到any类型时，我们可以将其断言为具体的类型，如果不处理的话，整个代码中将蔓延any
function getCacheData(key: string): any {
  return (window as any).cache[key];
}
interface Cat {
  name: string;
  run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();

// 类型断言的限制
// 类型断言不是说任意类型都可以被断言为其他任意类型的，只有兼容的两个类型之间才可以断言。
// 例如A兼容B，那么A就能被断言为B，同样B也能被断言为A；B兼容A，那么A就能被断言为B，同样B也能被断言为A

// 怎么理解兼容
// 对于联合类型来说，肯定是兼容对应的单一类型的，因此联合类型可以被断言为其中的一种类型
// ts是结构类型系统，类型之间的对比只会对比其最终的解构，会忽略他们定义时的关系
// 如果是类或接口之间存在继承关系，这种继承关系可以是显示的extends，也可以是隐式的（拥有相同属性或方法），那么此时父类是兼容子类的，也就可以断言
interface Animal {
  name: string;
}
interface Cat {
  name: string;
  run(): void;
}
// 等价于
interface Animal {
  name: string;
}
interface Cat extends Animal {
  run(): void;
}
// 那么Animal就兼容Cat，两者就可以进行类型断言

// 双重断言
// 既然任意类型可以被断言为any，然后any也可以被断言为任意类型，那么两个不兼容的类型就可以通过双重断言来实现
interface Cat {
  run(): void;
}
interface Fish {
  swim(): void;
}
function testCat(cat: Cat) {
  return (cat as any as Fish).swim();
} // 双重断言很容易出现运行时错误，若非必要，不建议使用双重断言

// 类型断言与类型转换的区别
// 类型断言只会影响ts编译时的类型，不会实际改变变量的类型，类型断言语句在编译结果中会被删除
// 类型转换会实际改变变量类型
function toBoolean(s: any): boolean {
  return s as boolean; // 原值返回
  // return Boolean(s); // 返回bool值
}
toBoolean(1)

// 类型断言与类型声明的区别
// 我们可以在声明变量时就指定其类型，也可以在声明变量后将其类型断言为某一类型，声明和断言很相似，但是还是存在一些差别
// A是否能断言为B，只需要满足A兼容B，或者B兼容A
// 但是如果想把A赋值给B，那么需要满足B兼容A，也就是说A应该是B的子类，不能把父类的实例直接赋值给子类
// 类型声明比类型断言更加严格，应该优先使用类型声明
interface Animal {
  name: string;
}
interface Cat {
  name: string;
  run(): void;
}
const animal: Animal = {
  name: 'tom',
  eat: function (): void {
    throw new Error("Function not implemented.");
  }
};
// let cat: Cat = animal; // 编译出错
let cat = animal as Cat; // 断言，编译正常

// 类型断言和泛型的区别
// 我们可以把函数改造为泛型，在实际调用时指定具体的类型，这样不经可以时间断言一样的效果，而且还可以避免出现any
function getCache<T>(key: string): T {
  return (window as any).cache[key]
}
interface Cat {
  name: string,
  run(): void
}
let catData = getCache<Cat>('tom');